﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OMap.Tools;


namespace OMap {

    /// <summary>
    /// Implements the logic for mapping property values from one object into another object.
    /// </summary>
    public sealed class CollectionMapper : BaseMapper,ICollectionMapper
    {

    	private Action<IList, object> _addItemAction;	

		/// <summary>
		/// The logic that should be executed when a new item should be added.
		/// </summary>
		/// <value></value>
		public Action<IList, object> AddItem {
			get {
				return _addItemAction;
			}
			set {
				_addItemAction = value;
			}
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="ElementMapper"/> class.
		/// </summary>
		/// <param name="sourceType">Type of the source.</param>
		/// <param name="targetType">Type of the target.</param>
		/// <param name="overwriteIfNotDefault">if set to <c>true</c> [overwrite if not default].</param>
		/// <param name="oMap">The o map.</param>
        public CollectionMapper(Type sourceType,Type targetType, bool overwriteIfNotDefault,OMap oMap)
			:this(sourceType,targetType,overwriteIfNotDefault,oMap,DefaultCreateTarget,DefaultAddItem) {
        }


		/// <summary>
		/// Initializes a new instance of the <see cref="ElementMapper"/> class.
		/// </summary>
		/// <param name="sourceType">Type of the source.</param>
		/// <param name="targetType">Type of the target.</param>
		/// <param name="overwriteIfNotDefault">If set to <c>true</c> values in the target object will be overwritten even if there is already a value in the target object.</param>
		/// <param name="oMap">The oMap Object.</param>
		/// <param name="createTarget">The logic that should be executed to create a new instance.</param>
		/// <param name="addItem">The logic that should be executed for adding a new item to the collection.</param>
		public CollectionMapper(Type sourceType, Type targetType, bool overwriteIfNotDefault, OMap oMap, Func<IObjectMapper, object> createTarget,Action<IList,object> addItem)
			: base(sourceType, targetType, overwriteIfNotDefault, oMap, createTarget) {
			this.CreateTarget = createTarget ?? DefaultCreateTarget;
			this.AddItem = addItem;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="ElementMapper"/> class.
		/// </summary>
		/// <param name="sourceType">Type of the source.</param>
		/// <param name="targetType">Type of the target.</param>
		/// <param name="overwriteIfNotDefault">If set to <c>true</c> values in the target object will be overwritten even if there is already a value in the target object.</param>
		/// <param name="oMap">The oMap Object.</param>
		/// <param name="addItem">The logic that should be executed for adding a new item to the collection.</param>
		public CollectionMapper(Type sourceType, Type targetType, bool overwriteIfNotDefault, OMap oMap, Action<IList, object> addItem)
			: this(sourceType, targetType, overwriteIfNotDefault, oMap, null,addItem) {
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="ElementMapper"/> class.
		/// </summary>
		/// <param name="sourceType">Type of the source.</param>
		/// <param name="targetType">Type of the target.</param>
		/// <param name="overwriteIfNotDefault">If set to <c>true</c> values in the target object will be overwritten even if there is already a value in the target object.</param>
		/// <param name="executeAddItem">if set to <c>true</c> [execute add item].</param>
		/// <param name="oMap">The oMap Object.</param>
		public CollectionMapper(Type sourceType, Type targetType, bool overwriteIfNotDefault,bool executeAddItem, OMap oMap)
			: this(sourceType, targetType, overwriteIfNotDefault, oMap, null,null){
			if(executeAddItem) {
				this._addItemAction = DefaultAddItem;
			}
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="ElementMapper"/> class.
		/// </summary>
		/// <param name="sourceType">Type of the source.</param>
		/// <param name="targetType">Type of the target.</param>
		/// <param name="overwriteIfNotDefault">If set to <c>true</c> values in the target object will be overwritten even if there is already a value in the target object.</param>
		/// <param name="executeAddItem">if set to <c>true</c> [execute add item].</param>
		public CollectionMapper(Type sourceType, Type targetType, bool overwriteIfNotDefault, bool executeAddItem)
			: this(sourceType, targetType, overwriteIfNotDefault,executeAddItem, null) {
		}


		/// <summary>
		/// Initializes the specified Root Mapping Object <see cref="OMap"/>.
		/// </summary>
		/// <param name="oMap">The root mapping object.</param>
		public override void Initialize(OMap oMap) {
			if (oMap != null)
			{
				this._oMap = oMap;
				this.IsInitialized = true;
			}
		}

    	/// <summary>
		/// Maps from the passed source object into a new target object.
		/// </summary>
		/// <param name="source">The source object that should be mapped into a new target object.</param>
		/// <param name="referenceDict">The reference dict.</param>
		/// <returns></returns>
        public override object MapTo(object source,Dictionary<object,object> referenceDict) {
            return MapTo(source, null,referenceDict);
        }

		/// <summary>
		/// Maps from the passed source object into an existing target object.
		/// </summary>
		/// <param name="source">The source object that should be mapped into a new target object.</param>
		/// <param name="target">The target object that should be used as mapping target.</param>
		/// <param name="referenceDict">The reference dict.</param>
		/// <returns></returns>
		public override object MapTo(object source, object target, Dictionary<object, object> referenceDict) {
			_mappedSourceValue = source;

            //if target value has a none default value and the overwrite target was not set to true
            //the target property wont be set.
            if (!TypeHelper.HasDefaultValue(target, _targetType) && !OverwriteTarget) {
            	_mappedTargetValue = target;
				return target;
            }

            //if sourcevalue equals target value or
            //if source value has the default value no mapping has to be done
            //and the targetvalue will be set to the sourcevalue
            if (source == target || TypeHelper.HasDefaultValue(source, SourceType)) {
            	_mappedTargetValue = source;
                return source;
            }

			if (!TypeHelper.HasDefaultValue(target, _targetType) && target is IList) {
				((IList)target).Clear();
			} else {
				//initialize custom func delegate to create a new collection instance
				target = CreateTarget(this);
			}

			_mappedTargetValue = target;

			IList targetList = target as IList;
            IEnumerable soureList = source as IEnumerable;
            Type targetValType = _targetType.HasElementType
                                     ? _targetType.GetElementType()
                                     : GetGenericCollectionType(_targetType);

            if(targetList == null || targetValType == null || soureList == null) {
                return null;
            }

            int targetIndex = 0;
            foreach (var sourceVal in (IEnumerable)source ) {
                var mapper = OMap.GetMapper(sourceVal.GetType(), targetValType, OverwriteTarget,true);
            	mapper.CalledBy = this;
               
				object targetVal = null;
				///////////////////////
				//Item Mapper ausführen
				///////////////////////
                if(targetList.Count > targetIndex) {
                    targetVal = targetList[targetIndex];
                    targetVal = mapper.MapTo(sourceVal,targetVal,referenceDict);
                }else {
                    targetVal = mapper.MapTo(sourceVal,referenceDict);
                }

                targetIndex++;
                if (this.AddItem != null) {
					AddItem(targetList, targetVal);
				}
            }

            return target;
        }

		

    	/// <summary>
		/// Gets the type of the generic collection.
		/// </summary>
		/// <param name="collectionType">Type of the collection.</param>
		/// <returns></returns>
		private static Type GetGenericCollectionType(Type collectionType) {
			foreach(Type interfaceType in collectionType.GetInterfaces()) {
				Type genericType = interfaceType.GetGenericArguments().FirstOrDefault();
				if(genericType != null) {
					return genericType;
				}
			}
			return null;
		}


		/// <summary>
		/// The default logic for adding new items to the list.
		/// </summary>
		/// <returns></returns>
		private static void DefaultAddItem(IList targetList,object newItem) {
			targetList.Add(newItem);
		}
		

		/// <summary>
		/// Creates a new collection object based on the target type.
		/// </summary>
		/// <returns></returns>
		private static object DefaultCreateTarget(IObjectMapper mapper) {
			if (mapper.TargetType == null) {
				throw new InvalidOperationException("TargetType can not be nulll");
			}

			if (mapper.TargetType == typeof(IList)) {
				return new ArrayList();
			} else if (mapper.TargetType.IsGenericType && typeof(IList<>).IsAssignableFrom(mapper.TargetType.GetGenericTypeDefinition())) {
				Type genListType = typeof(List<>);
				Type[] typeArgs = mapper.TargetType.GetGenericArguments();
				Type genericType = genListType.MakeGenericType(typeArgs);

				return Activator.CreateInstance(genericType);
			} else if (!mapper.TargetType.IsAbstract) {
				return TypeHelper.CreateDefaultInstance(mapper.TargetType);
			}
			return null;
		}
	}
}
